package net.aiion.weave.impl.bytecode;

import java.io.IOException;
import java.io.InputStream;

import net.aiion.weave.asm.ClassReader;
import net.aiion.weave.impl.bytecode.asm.BytecodeASMAction;
import net.aiion.weave.impl.bytecode.asm.BytecodeASMClassVisitor;
import net.aiion.weave.impl.bytecode.events.BytecodeEventImpl;
import net.aiion.weave.spi.bytecode.Bytecode;
import net.aiion.weave.spi.bytecode.BytecodeException;
import net.aiion.weave.spi.bytecode.ResumptionCondition;
import net.aiion.weave.spi.bytecode.events.BytecodeEvent;
import net.aiion.weave.spi.resources.Resource;

public class BytecodeReaderImpl extends AbstractBytecodeSource {
    private final Resource resource;

    private ClassReader classReader;
    private BytecodeASMClassVisitor classVisitor;

    private boolean isStarted;
    private boolean isPostponed;
    private boolean isCancelled;

    private ResumptionCondition resumptionCondition;

    public BytecodeReaderImpl(final Resource aResource) {
        resource = aResource;
    }

    @Override
    public void startSourcing() {
        isStarted = true;

        prepareSourcing();

        try (InputStream is = resource.openStream()) {
            classVisitor = new BytecodeASMClassVisitor(Bytecode.getEventFactory(), new DispatchBytecodeAction());
            classReader = new ClassReader(is);
            classReader.accept(classVisitor, 0);
        } catch (final IOException e) {
            throw new BytecodeException(e);
        } finally {
            classReader = null;
            classVisitor = null;
        }

        finishSourcing();
    }

    public void prepareSourcing() {
    }

    public void finishSourcing() {
    }

    @Override
    public void postponeSourcing() {
        postponeSourcing(new ResumptionCondition() {
            @Override
            public boolean shouldResume() {
                return true;
            }
        });
    }

    @Override
    public void postponeSourcing(final ResumptionCondition aResumptionCondition) {
        isPostponed = true;

        if (classVisitor != null) {
            classVisitor.cancelVisitation();
        }

        resumptionCondition = aResumptionCondition;
    }

    @Override
    public void cancelSourcing() {
        isCancelled = true;

        if (classVisitor != null) {
            classVisitor.cancelVisitation();
        }
    }

    @Override
    public boolean isStarted() {
        return isStarted && !(isPostponed || isCancelled);
    }

    @Override
    public boolean isPostponed() {
        return isPostponed;
    }

    @Override
    public boolean isCancelled() {
        return isCancelled;
    }

    @Override
    public ResumptionCondition getResumptionCondition() {
        return resumptionCondition;
    }

    private class DispatchBytecodeAction implements BytecodeASMAction {
        public DispatchBytecodeAction() {
        }

        @Override
        public void processBytecode(final BytecodeEvent aBytecodeEvent) {
            if (isPostponed || isCancelled) {
                return;
            }

            if (aBytecodeEvent instanceof BytecodeEventImpl) {
                ((BytecodeEventImpl) aBytecodeEvent).setNatural(true);
            }

            dispatchBytecodeEvent(aBytecodeEvent);
        }
    }
}
